Kakao登录

kakao 介绍

Kakao 为韩国最大通讯公司

Android 集成文档:
https://developers.kakao.com/docs/latest/en/kakaologin/android

MyApplication 设置

App Key 获取

https://developers.kakao.com/console/app
[My Application] > [App Keys] or [My Application] > [Summary]
bkmzh
APP 关注 Native app key

Platform 设置

以 Android 为例:

iicpg
pl2z4

Kakao Login

Kakao Login Activation

需要开启 Kakao Login Activation,否则获取不到 AccessToken
onef7

OpenID Connect Activation

nvnqf

94yw4
右上角可以 preview:
b20xb

初始化

class GlobalApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    // Other codes for initialization 

    // Initialize Android SDK 
    KakaoSdk.init(this, "${YOUR_NATIVE_APP_KEY}")
  }
}

Before using Kakao Login APIs with the Android SDK,

  1. Register the Android platform.
  2. Add modules.
  3. Set Redirect URI.

KaKao 注册平台登记后才能使用:[My Application] > [Platform] > Android/iOS/Web
d6d7b

keytool -exportcert -alias <RELEASE_KEY_ALIAS> -keystore <RELEASE_KEY_PATH> | openssl sha1 -binary | openssl base64
类似:8tG0AIdXBAyX5aN8w83ECrKYMxxx

示例:
rxd2w

// app build.gradle
// Allprojects not available in the latest Android Studio
allprojects {    
    repositories {        
        google()        
        jcenter()        
        maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/'}    
    }
}


// settings.gradle
dependencyResolutionManagement {    
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)    
        repositories {        
            google()        
            mavenCentral()        
            maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }    
    }
}

// modules to the build.gradle(module-level) file.
dependencies {
  implementation "com.kakao.sdk:v2-all:2.18.0" // Add all modules (Available in 2.11.0 or higher)

  implementation "com.kakao.sdk:v2-user:2.18.0" // Kakao Login
  implementation "com.kakao.sdk:v2-talk:2.18.0" // Kakao Talk Social, Kakao Talk Messaging
  implementation "com.kakao.sdk:v2-friend:2.18.0" // Friend picker
  implementation "com.kakao.sdk:v2-share:2.18.0" // Kakao Talk Sharing
  implementation "com.kakao.sdk:v2-navi:2.18.0" // Kakao Navi 
  implementation "com.kakao.sdk:v2-cert:2.18.0" // Kakao Certificate
}
# kakao https://developers.kakao.com/docs/latest/en/android/getting-started#project-pro-guard
-keep class com.kakao.sdk.**.model.* { <fields>; }
-keep class * extends com.google.gson.TypeAdapter

# https://github.com/square/okhttp/pull/6792
-dontwarn org.bouncycastle.jsse.**
-dontwarn org.conscrypt.*
-dontwarn org.openjsse.**

refscheme 填 kakao${YOUR_NATIVE_APP_KEY}

<activity
    android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <!-- Redirect URI: "kakao${YOUR_NATIVE_APP_KEY}://oauth" -->
        <data
            android:host="oauth"
            android:scheme="kakaob70bd3c3f3cdab01ccfe4cdd7f80542b" />
    </intent-filter>
</activity>

代码:

object KaKaoLoginHelper {

    private const val TAG = "KaKaoLogin"

    // 测试的app key
    private const val APP_KEY = "b70bd3c3f3cdab01ccfe4cdd7f80542b"

    fun init(context: Context) {
        KakaoSdk.init(context, APP_KEY)
    }

    /**
     * Check if KakaoTalk is installed on user's device
     *
     * @return true if KakaoTalk is installed on user's device, false otherwise
     */
    fun isKakaoTalkLoginAvailable(context: Context): Boolean {
        return UserApiClient.instance.isKakaoTalkLoginAvailable(context = context)
    }

    /**
     * Login with Kakao Account/KakaoTalk
     */
    fun login(context: Context, callback: (User?, Throwable?) -> Unit) {
        // Login common callback
        val internalCallback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
            if (error != null) {
                // 登录过程中发生错误
                loge("login 登录过程中发生错误:${error.message}", error)
                error.printStackTrace()
                callback.invoke(null, error)
            } else if (token != null) {
                // 登录成功,token 包含了访问令牌

                Log.i(TAG, "login 登录成功,开始检索用户信息,token 包含了访问令牌:$token")
                // 检索用户信息
                getUserInfo { user: User?, throwable: Throwable? ->
                    if (throwable != null) {
//                        loge("login 登录成功,获取用户信息失败:${throwable.message}", throwable)
                        throwable.printStackTrace()
                        callback.invoke(null, throwable)
                    } else if (user != null) {
//                        logi("login 登录成功,检索用户信息成功,user(${user.id}):$user")
                        callback.invoke(user, null)
                    }
                }
            }
        }
        // If Kakao Talk is installed on user's device, proceed to log in with Kakao Talk. Otherwise, implement to log in with Kakao Account.
        if (isKakaoTalkLoginAvailable(context)) {
            logd("login with KakaoTalk")
            UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
                if (error != null) {
                    // After installing Kakao Talk, if a user does not complete app permission and cancels Login with Kakao Talk, skip to log in with Kakao Account, considering that the user does not want to log in.
                    // You could implement other actions such as going back to the previous page.
                    if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
                        loge("login with KakaoTalk failed cancelled: ${error.message}", error)
                        return@loginWithKakaoTalk
                    }
                    loge(
                        "login with KakaoTalk failed, attempt loginWithKakaoAccount, error: ${error.message}",
                        error
                    )

                    // If a user is not logged into Kakao Talk after installing Kakao Talk and allowing app permission, make the user log in with Kakao Account.
                    UserApiClient.instance.loginWithKakaoAccount(
                        context,
                        callback = internalCallback,
                    )
                } else if (token != null) {
                    logd("login with KakaoTalk succeeded, accessToken=${token.accessToken}")
                    internalCallback.invoke(token, null)
                }
            }
        } else {
            logd("login with KakaoAccount")
            UserApiClient.instance.loginWithKakaoAccount(context, callback = internalCallback)
        }
    }

    fun loginWithKakaoTalk(context: Context, callback: (User?, Throwable?) -> Unit) {
        // Login with Kakao Talk
        UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
            if (error != null) {
                loge("loginWithKakaoTalk failed.", error)
                callback.invoke(null, error)
            } else if (token != null) {
                logi("loginWithKakaoTalk succeeded.${token.accessToken}")
                // 检索用户信息
                getUserInfo { user: User?, throwable: Throwable? ->
                    if (throwable != null) {
                        // 获取用户信息失败
//                        loge("loginWithKakaoTalk 登录成功,获取用户信息失败:${throwable.message}", throwable)
                        throwable.printStackTrace()
                        callback.invoke(null, throwable)
                    } else if (user != null) {
//                        logi("loginWithKakaoTalk 登录成功,检索用户信息成功,user(${user.id}):$user")
                        callback.invoke(user, null)
                    }
                }
            }
        }
    }

    fun loginWithKakao(context: Context, callback: (User?, Throwable?) -> Unit) {
        UserApiClient.instance.loginWithKakaoAccount(context) { token, error ->
            if (error != null) {
                // 登录过程中发生错误
                loge("loginWithKakao 登录过程中发生错误:${error.message}", error)
                error.printStackTrace()
                callback.invoke(null, error)
            } else if (token != null) {
                // 登录成功,token 包含了访问令牌

//                logi("loginWithKakao 登录成功,开始检索用户信息,token 包含了访问令牌:$token")
                // 检索用户信息
                getUserInfo { user: User?, throwable: Throwable? ->
                    if (throwable != null) {
//                        loge("loginWithKakao 登录成功,获取用户信息失败:${throwable.message}", throwable)
                        throwable.printStackTrace()
                        callback.invoke(null, throwable)
                    } else if (user != null) {
//                        logi("loginWithKakao 登录成功,检索用户信息成功,user(${user.id}):$user")
                        callback.invoke(user, null)
                    }
                }
            }
        }
    }

    /**
     * Retrieve user information
     *
     * https://developers.kakao.com/docs/latest/en/kakaologin/android#req-user-info
     */
    fun getUserInfo(callback: (User?, Throwable?) -> Unit = { _, _ -> }) {
        UserApiClient.instance.me { user: User?, throwable: Throwable? ->
            if (throwable != null) {
                // 获取用户信息失败
                loge("getUserInfo 登录成功,获取用户信息失败:${throwable.message}", throwable)
                throwable.printStackTrace()
                callback.invoke(null, throwable)
            } else if (user != null) {
                // 获取用户信息成功,这里可以处理用户信息
                logi("getUserInfo 登录成功,检索用户信息成功,user(${user.id}):$user")
                callback.invoke(user, null)
            }
        }
    }

    /**
     * Retrieve token information
     *
     * https://developers.kakao.com/docs/latest/en/kakaologin/android#get-token-info
     */
    fun accessTokenInfo(callback: (tokenInfo: AccessTokenInfo?, error: Throwable?) -> Unit) {
        // However, note that the return value true does not guarantee that the user is in a logged-in state.
        if (AuthApiClient.instance.hasToken()) {
            UserApiClient.instance.accessTokenInfo { tokenInfo, error ->
                if (error != null) {
                    loge("accessTokenInfo Failed to retrieve token information.", error)
                } else if (tokenInfo != null) {
                    logi(
                        "accessTokenInfo Retrieving token information success" +
                                "\n App ID: ${tokenInfo.appId}" +
                                "\nService user ID: ${tokenInfo.id}" +
                                "\nValidity period: ${tokenInfo.expiresIn} seconds"
                    )
                }
                callback.invoke(tokenInfo, error)
            }
        } else {
            loge("accessTokenInfo Failed to retrieve token information. No token.")
            callback.invoke(null, null)
        }
    }

    /**
     * Logout, Regardless of the result of the logout request, the Android SDK deletes the access and refresh tokens and has the login session end.
     *
     * deletes the access and refresh tokens issued to the user.
     *
     * https://developers.kakao.com/docs/latest/en/kakaologin/android#logout
     */
    fun logout(callback: (Throwable?) -> Unit) {
        UserApiClient.instance.logout { error ->
            if (error != null) {
                loge("Logout failed: ${error.message}", error)
                callback.invoke(error)
            } else {
                logi("Logout succeeded")
                callback.invoke(null)
            }
        }
    }

    /**
     * Unlink
     *
     * If the request is successful, the Android SDK deletes the access and refresh tokens. As the issued tokens are deleted, the session between an app and a user is disconnected, and the user is logged out and unlinked from your app.
     *
     * https://developers.kakao.com/docs/latest/en/kakaologin/android#unlink
     */
    fun unlink(callback: (error: Throwable?) -> Unit) {
        UserApiClient.instance.unlink { error ->
            callback.invoke(error)
            if (error != null) {
                loge("Unlink fail", error)
            } else {
                logi("Unlink success. Tokens are deleted from SDK.")
            }
        }
    }

    fun loge(msg: String, e: Throwable? = null) {
        Log.e(TAG, msg, e)
    }

    fun logi(msg: String) {
        Log.i(TAG, msg)
    }

    fun logd(msg: String) {
        Log.d(TAG, msg)
    }
}

kakao 登录授权弹窗
5mmsh

  1. 拿到 access_token 会拿到 userid
  2. 拿 userid 给中间层登录

遇到的问题

管理员设置问题导致授权不了

没有开启 kakao 登录
93yyy
解决:kakao 应用后台开启
7upx7

Error due to incorrect platform information:invalid android_key_hash or ios_bundle_id or web_site_url

解决:错误:invalid android_key_hash or ios_bundle_id or web_site_url
应用设置后台看看有没有注册对应的登录平台

Kakao SDK 评估

背景

亚洲站点、韩国市场,添加三方登录 Kakao

版本分析

新接入的 sdk,没有历史接入版本

影响范围

新功能,功能异常情况下会影响到 Kakao 用户的三方登录,需要做 abt 降级

价值评估

提升亚洲站点、韩国市场用户登录转化率

影响评估

App 质量

性能

待测试

包大小

aar 大小 (com.kakao.sdk:v2-user:2.18.0):342K

  1. user 103K
  2. network 22K
  3. common 90K
  4. auth 127K

兼容性

业务功能影响

新功能,功能异常情况下会影响到 Kakao 用户的三方登录,需要做 abt 降级

技术方案评估

UI、UE

无影响

接口

无影响

效果监控

不实施

风险评估

电子应用市场审核规则风险评估

待评估

安全评估

降级方案

隐藏 Kakao 登录方式